package solver

import (
	"gosudoku/set"
	"log"
)

type Discard struct{}

func (Discard) Write(p []byte) (n int, err error) {
	return
}

func init() {
	log.SetFlags(log.Lmicroseconds)
	log.SetPrefix("Sudoku: ")
	//log.SetOutput(Discard{})

}

func merge[T any](r [][]T, x T) [][]T {
	if len(r) == 0 {
		return [][]T{{x}}
	}
	for i, xs := range r {
		r[i] = append([]T{x}, xs...)
	}
	return r
}

func combine[T any](xs []T, n int) [][]T {
	if n < 1 {
		return [][]T{}
	}
	if n == 1 {
		r := [][]T{}
		for _, x := range xs {
			r = append(r, []T{x})
		}
		return r
	}
	length := len(xs)
	if length < n {
		return [][]T{}
	}
	if length == n {
		return [][]T{xs}
	}
	return append(merge(combine(xs[1:], n-1), xs[0]), combine(xs[1:], n)...)
}

/*
	func sumupCandCells(house House) (candCells [9][9]bool, candCounts [9]int) {
		for cellNum, cell := range house.cells {
			if cell.solved() {
				continue
			}
			for cand, boolCand := range cell.cands {
				if boolCand {
					candCells[cand][cellNum] = true
					candCounts[cand]++
				}
			}
		}
		return
	}
*/
func (g *Grid) doStrategy(doStrategyHouse func(House) bool) (used bool) {
	for _, box := range g.boxes {
		if doStrategyHouse(box) {
			used = true
			return
		}
	}

	for _, row := range g.rows {
		if doStrategyHouse(row) {
			used = true
			return
		}
	}

	for _, col := range g.cols {
		if doStrategyHouse(col) {
			used = true
			return
		}
	}
	return
}

/*
	func intersect(as []int, bs []int) []int {
		cs := []int{}
		for _, a := range as {
			if slices.Contains(bs, a) {
				cs = append(cs, a)
			}
		}
		return cs
	}

	func union(as []int, bs []int) []int {
		cs := []int{}
		cs = append(cs, as...)
		cs = append(cs, bs...)
		slices.Sort(cs)
		cs = slices.Compact(cs)
		return cs
	}

	func difference(as []int, bs []int) []int {
		cs := []int{}
		for _, a := range as {
			if !slices.Contains(bs, a) {
				cs = append(cs, a)
			}
		}
		return cs
	}

	func symDifference(as []int, bs []int) []int {
		return difference(union(as, bs), intersect(as, bs))
	}
*/
func (g *Grid) validateGivens() bool {
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if g.givens[i][j] != 0 && g.givens[i][j] != g.cells[i][j].value {
				log.Printf("r%vc%v givens=%v solved=%v\n", i, j, g.givens[i][j], g.cells[i][j].value)
				return false
			}
		}
	}
	return true
}

func (g *Grid) Validate() bool {
	if g.solvedCount != 81 {
		log.Println("solvedCount:", g.solvedCount)
		return false
	}

	if !g.validateGivens() {
		log.Println("the givens are modified")
		return false
	}

	for rowNum, row := range g.rows {
		if !validateHouse(row) {
			log.Printf("row %v error\n", rowNum)
			return false
		}
	}

	for colNum, col := range g.cols {
		if !validateHouse(col) {
			log.Printf("col %v error\n", colNum)
			return false
		}
	}

	for boxNum, box := range g.boxes {
		if !validateHouse(box) {
			log.Printf("box %v error\n", boxNum)
			return false
		}
	}
	return true
}

func validateHouse(house House) (ok bool) {
	ok = true

	if house.solvedCount != 9 {
		ok = false
		log.Println("house.solvedCount:", house.solvedCount)
	}

	values := set.NewSet()
	for _, cell := range house.cells {
		if 1 <= cell.value && cell.value <= 9 {
			values.Add(toCand(cell.value))
		}
	}

	if values != set.NewSet([]int{0, 1, 2, 3, 4, 5, 6, 7, 8}) {
		ok = false
		log.Println("house.values:", values)
	}

	return
}

func (g *Grid) seenCells(c *Cell) []*Cell {
	cells := []*Cell{}
	for _, cell := range g.boxes[c.boxNum].cells {
		if !cell.Equal(c) {
			cells = append(cells, cell)
		}
	}
	for _, cell := range g.rows[c.rowNum].cells {
		if cell.boxNum != c.boxNum {
			cells = append(cells, cell)
		}
	}
	for _, cell := range g.cols[c.colNum].cells {
		if cell.boxNum != c.boxNum {
			cells = append(cells, cell)
		}
	}
	return cells
}

func shareCells(cs1 []*Cell, cs2 []*Cell) []*Cell {
	shared := make([]*Cell, 0)
	for _, c1 := range cs1 {
		for _, c2 := range cs2 {
			if c1.Equal(c2) {
				shared = append(shared, c1)
			}
		}
	}
	return shared
}

func chooseHasCandCells(house House, cand int) (cells []*Cell) {
	for _, cell := range house.cells {
		if cell.cands.Has(cand) {
			cells = append(cells, cell)
		}
	}
	return
}
